package output.store.view.component;
//3D scope

import java.io.*;
import javax.imageio.ImageIO;
import java.awt. * ;
import java.awt.event. * ;
import javax.swing. * ;
import java.awt.image.BufferedImage;

import output.store.view.base.Statics;
import output.store.view.base.FrameStack;
import output.store.view.base.Frame;
import output.store.view.component.keyboard.KeyboardInput;

public class View3D extends JFrame
{
	private View3DCanvas canvas = new View3DCanvas(this);
	
	private int lastCurrentFrame = 0;
	
	private KeyboardInput keyboard;
	
	public View3D()
	{
		super("View3D");
		
		setLayout(null);
		
		add(canvas);
		canvas.setBounds(18, 6, 800, 600);
		setSize(850, 650);
		//pack();
		
		//setVisible(true);
		
		keyboard = new KeyboardInput();
		this.addKeyListener(keyboard);
		canvas.addKeyListener(keyboard);
	}
	
	public void refresh()
	{
		if(lastCurrentFrame != Statics.currentFrame)
		{
			setTitle("View3D time: " + String.format("%.6f", FrameStack.frames[Statics.currentFrame].time) + " s");
			
			canvas.myPaint();
		}
	}
	
	private int shootCount = 0;
	public synchronized void shoot()
	{
		// Poll the keyboard
		keyboard.poll();
		
		if(keyboard.keyDown(KeyEvent.VK_SPACE) == false)
		{
			return;
		}
		
		canvas.myPaint();
		
		BufferedImage img = canvas.image;
		try
		{
			File outputfile = new File("output/store/view/.pic/img" + shootCount + ".png");
			ImageIO.write(img, "png", outputfile);
			System.out.println("saved picture: \t" + ".pic/img" + shootCount);
			shootCount++;
		}
		catch(IOException e)
		{
			System.out.println("save picture failed!");
		}
	}
}

class View3DCanvas extends JPanel
{
	public View3D father;
	public int[] size = new int[] {800, 600};
	public int dist = 400;
	public double radius = 30;
	public double theta = 0.1;
	public double phi = -0.05;
	public long lastPaint = 0;
	
	public static double[][] phaseColor = new double[][]
	{
		{255, 150, 150},
		{150, 150, 255},
	};
	
	BufferedImage image = new BufferedImage(size[0], size[1], BufferedImage.TYPE_INT_RGB);
	Graphics2D graphics = (Graphics2D)image.getGraphics();
	
	
	public View3DCanvas(View3D _father)
	{
		father = _father;
		
		MouseHandler mh = new MouseHandler();
		addMouseListener(mh);
		addMouseMotionListener(mh);
		addMouseWheelListener(mh);
	}
	
	public synchronized void myPaint()
	{
		if(System.currentTimeMillis() - lastPaint < 10)
		{return;}
		
		graphics.setColor(new Color(255, 255, 255));
		graphics.fillRect(0, 0, size[0], size[1]);
		
		Frame frame = FrameStack.frames[Statics.currentFrame];
		
		double[] dist2 = new double[frame.particleCount];
		int[] num = new int[frame.particleCount];
		for(int i = 0; i < frame.particleCount; i++)
		{
			num[i] = i;
			dist2[i] = calcDist2(frame.pos[i]);
		}
		sortDist(dist2, num, 0, frame.particleCount);
		
		for(int i = 0; i < num.length; i++)
		{
			int pointer = num[i];
			double[] newPos = calcPos(frame.pos[pointer]);
			if(newPos[0] < radius)
			{
				double x = dist * newPos[1] / (radius - newPos[0]);
				double y = dist * newPos[2] / (radius - newPos[0]);
				
				double[] color = new double[3];
				for(int j = 0; j < 2; j++)
				{
					color = View3DAid.vecAdd(color, View3DAid.vecMul(phaseColor[j], frame.phaseVolume[pointer][j]));
				}
				
				graphics.setColor(new Color((int)color[0], (int)color[1], (int)color[2]));
				graphics.fillRect((int)(x + size[0] / 2 - 1), (int)(-y + size[1] / 2 - 1), 2, 2);
			}
		}
		
		double[] posX = calcPos(new double[] {0.6 * radius, 0, 0});
		double[] posY = calcPos(new double[] {0, 0.6 * radius, 0});
		double[] posZ = calcPos(new double[] {0, 0, 0.6 * radius});
		
		graphics.setColor(new Color(255, 0, 0));
		double x = dist * posX[1] / (radius - posX[0]);
		double y = dist * posX[2] / (radius - posX[0]);
		graphics.drawLine((int)(x + size[0] / 2 - 1), (int)(-y + size[1] / 2 - 1), (int)(size[0] / 2 - 1), (int)(size[1] / 2 - 1));
		
		graphics.setColor(new Color(0, 255, 0));
		x = dist * posY[1] / (radius - posY[0]);
		y = dist * posY[2] / (radius - posY[0]);
		graphics.drawLine((int)(x + size[0] / 2 - 1), (int)(-y + size[1] / 2 - 1), (int)(size[0] / 2 - 1), (int)(size[1] / 2 - 1));
		
		graphics.setColor(new Color(0, 0, 255));
		x = dist * posZ[1] / (radius - posZ[0]);
		y = dist * posZ[2] / (radius - posZ[0]);
		graphics.drawLine((int)(x + size[0] / 2 - 1), (int)(-y + size[1] / 2 - 1), (int)(size[0] / 2 - 1), (int)(size[1] / 2 - 1));
		
		graphics.setColor(new Color(50, 50, 255));
		Font stringFont = new Font("Arial", Font.PLAIN, 24);
		graphics.setFont(stringFont);
		graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_GASP);
		
		String status = "time = " + String.format("%.4f", frame.time) + " s";
		drawString(graphics, status, 10, 0);
		
		graphics.setColor(new Color(0, 0, 0));
		graphics.drawRect(0, 0, size[0] - 1, size[1] - 1);
		this.repaint();
		
		lastPaint = System.currentTimeMillis();
	}
	
	private void drawString(Graphics2D g, String text, int x, int y)
	{
		for(String line : text.split("\n"))
		{ g.drawString(line, x, y += g.getFontMetrics().getHeight()); }
	}
	
	public void paint(Graphics g)
	{
		g.drawImage(image, 0, 0, null);
	}
	
	private double[] calcPos(double[] _pos)
	{
		double[] newPos = new double[3];
		newPos[0] = _pos[0] * Math.cos(theta) * Math.cos(phi) - _pos[1] * Math.cos(theta) * Math.sin(phi) + _pos[2] * Math.sin(theta);
		newPos[1] = _pos[0] * Math.sin(phi) + _pos[1] * Math.cos(phi);
		newPos[2] = _pos[2] * Math.cos(theta) - _pos[0] * Math.cos(phi) * Math.sin(theta) + _pos[1] * Math.sin(theta) * Math.sin(phi);
		return newPos;
	}
	
	private double calcDist2(double[] _pos)
	{
		double[] newPos = calcPos(_pos);
		double dx = newPos[0] - radius;
		double dy = newPos[1];
		double dz = newPos[2];
		return dx * dx + dy * dy + dz * dz;
	}
	
	private void sortDist(double[] dist, int[] num, int start, int end)
	{
		int mid = (start + end) / 2;
		if(end - start > 2)
		{
			sortDist(dist, num, start, mid);
			sortDist(dist, num, mid, end);
		}
		int pointer1 = start;
		int pointer2 = mid;
		double[] _dist = new double[end - start];
		int[] _num = new int[end - start];
		for(int i = 0; i < end - start; i++)
		{
			if(pointer1 == mid)
			{
				_dist[i] = dist[pointer2];
				_num[i] = num[pointer2];
				pointer2++;
			}
			else if(pointer2 == end)
			{
				_dist[i] = dist[pointer1];
				_num[i] = num[pointer1];
				pointer1++;
			}
			else if(dist[pointer1] >= dist[pointer2])
			{
				_dist[i] = dist[pointer1];
				_num[i] = num[pointer1];
				pointer1++;
			}
			else
			{
				_dist[i] = dist[pointer2];
				_num[i] = num[pointer2];
				pointer2++;
			}
		}
		for(int i = 0; i < end - start; i++)
		{
			dist[start + i] = _dist[i];
			num[start + i] = _num[i];
		}
	}
	
	private class MouseHandler extends MouseAdapter implements MouseWheelListener
	{
		private double oldTheta;
		private double oldPhi;
		Point start = new Point(0, 0);
		Point current = new Point(0, 0);
		@Override
		public void mousePressed(MouseEvent me)
		{
			oldTheta = theta;
			oldPhi = phi;
			start = me.getPoint();
		}
		@Override
		public void mouseDragged(MouseEvent me)
		{
			int dx = me.getX() - start.x;
			int dy = me.getY() - start.y;
			phi = oldPhi + 0.008 * dx;
			theta = oldTheta + 0.008 * dy;
			if(theta > 0.5 * Math.PI)
			{theta = 0.5 * Math.PI;}
			if(theta < -0.5 * Math.PI)
			{theta = -0.5 * Math.PI;}
			myPaint();
		}
		@Override
		public void mouseWheelMoved(MouseWheelEvent e)
		{
			if(e.getWheelRotation() == 1)
			{
				radius /= 1.1;
			}
			if(e.getWheelRotation() == -1)
			{
				radius *= 1.1;
			}
			myPaint();
		}
	}
}

class View3DAid
{
	public static double[] vecAdd(double[] vec1, double[] vec2)
	{
		double[] ans = new double[3];
		ans[0] = vec1[0] + vec2[0];
		ans[1] = vec1[1] + vec2[1];
		ans[2] = vec1[2] + vec2[2];
		return ans;
	}
	
	public static double[] vecMul(double[] vec1, double x)
	{
		double[] ans = new double[3];
		ans[0] = vec1[0] * x;
		ans[1] = vec1[1] * x;
		ans[2] = vec1[2] * x;
		return ans;
	}
}